/*
 *	This file is part of Lounge-Cast.
 *
 *  Author:  	Markus Moormann
 *	E-mail:  	mmoorman@uni-osnabrueck.de
 *	Copyright:	University of Osnabrueck (Zentrum Virtuos)
 * 	Website:	www.uni-osnabrueck.de
 *	Date:		16.02.2011
 *
 *	Lounge-Cast is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *	Lounge-Cast is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Lounge-Cast.  If not, see <http://www.gnu.org/licenses/>.
 */
package backend.watcher;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.SocketException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.FileUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.telnet.TelnetClient;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import backend.dbConnection.ConnectionHelper;
import backend.helper.HelperClass;

/**
 * class loads up a slide to a server waits for the conversion, 
 * downloads neccassary files and combines the back- and foreground
 * to one single SWF
 * 
 * @author Markus Moormann
 */
public class SlideWatcher extends Thread{
	
	/**
	 * a logger to log errors, debug information, etc.
	 */
	private static Logger LOG = Logger.getLogger(SlideWatcher.class);
	/**
	 * the media_id of the slide
	 */
	private String media_id;
	/**
	 * the file containing the slide
	 */
	private File slide;
	/**
	 * the directory where to upload the slide
	 */
	private String directory;
	/**
	 * start time of conversion
	 */
	private long startTime;
	/**
	 * the directory where to download the converted files
	 */
	private String downloadDirectory;
	/**
	 * the ftpClient
	 */
	private FTPClient ftp;
	/**
	 * user name to login  
	 */
	private String username;
	/**
	 * the password belonging to the user
	 */
	private String password;
	/**
	 * System seperator
	 */
	private String sep = File.separator;
	/**
	 * the URL to the ppt
	 */
	private String url;
	
	/**
	 * a list of commands that should be executed
	 */
	private List<String> commands = new LinkedList<String>();

	/**
	 * constructor
	 * 
	 * @param media_id the media_id of the slide
	 * @param slide the slide itself
	 */
	public SlideWatcher(String media_id, File slide) {
		this.media_id = media_id;
		this.slide = slide;
		this.directory = "Flurfunk-" + media_id;
		this.startTime = System.currentTimeMillis();
		this.downloadDirectory = HelperClass.getProperty("generalUploadFolder") + File.separator + 
									HelperClass.getProperty("slideSubFolder") + File.separator + media_id;
		this.username = "virtpresenter";
		this.password = "soutriv";
	}
	
	/**
	 * constructor
	 * 
	 * @param media_id the media_id of the slide
	 * @param url the URL pointing to the powerpoint file
	 */
	public SlideWatcher(String media_id, String url) {
		this.media_id = media_id;
		this.directory = "Flurfunk-" + media_id;
		this.url = url;
		this.startTime = System.currentTimeMillis();
		this.downloadDirectory = HelperClass.getProperty("generalUploadFolder") + File.separator + 
									HelperClass.getProperty("slideSubFolder") + File.separator + media_id;
		this.username = "virtpresenter";
		this.password = "soutriv";
	}
	
	/**
	 * starts the upload/conversion and download
	 */
	public void run() {
		if(HelperClass.getExtension(slide).equals("pdf"))
		{
			if(!convertPDF2SWF())
			{
				LOG.error("conversion of PDF failed");
				return;
			}
			if(!createViewerXML())
			{
				LOG.error("Creation of viewer.xml failed");
				return;
			}
			LOG.debug("updating conversion status in database");
            Connection connection;
			try {
				connection = ConnectionHelper.getConnection();
				Statement statement = connection.createStatement();
	            String updateMedia = "UPDATE " +
	            						"media " +
	            					 "SET " +
	            					 	"converted = 'true' " +
	            					 "WHERE " +
	            					 	"media_id = '" + media_id + "'";
	            statement.executeUpdate(updateMedia);
	            ConnectionHelper.close(statement);
			} catch (SQLException e) {
				LOG.error("SQLException", e);
			}
			return;
		}
		
		//download the file first then start uploading and the other stuff
		if(url != null)
		{
			this.slide = downloadPPT(this.url, this.media_id);
		}
		//try to upload
		if(!upload()) {
			LOG.error("upload went wrong - for further information look for info above");
			return;
		}
		//try to start conversion
		if(!startConversion()) {
			LOG.error("could not start conversion - for further information look for info above\n" +
						"try starting conversion manualy");
			return;
		}
		
		try {
			//wait while conversion is done
			while(!checkForConversion()) {
				LOG.debug("Conversion not yet ready after " + (System.currentTimeMillis() - startTime) / 1000 + "s");
				if(System.currentTimeMillis() - startTime > 30 * 60 * 1000) {
					LOG.error("Conversion not ready after long time. Check Server!");
					return;
				}
				//wait for one minute
				sleep(1 * 60 * 1000);
			}
			//download files and update status in database
			if(downloadFiles()) {
				//combine the back- and foreground
				if(swfCombine())
				{
					LOG.debug("updating conversion status in database");
		            Connection connection = ConnectionHelper.getConnection();
		            Statement statement = connection.createStatement();
		            String updateMedia = "UPDATE " +
		            						"media " +
		            					 "SET " +
		            					 	"converted = 'true' " +
		            					 "WHERE " +
		            					 	"media_id = '" + media_id + "'";
		            statement.executeUpdate(updateMedia);
		            ConnectionHelper.close(statement);
		            
				}
			}
		} catch (InterruptedException e) {
			LOG.error(e);
		} catch (SQLException e) {
			LOG.error(e);
		} catch (JDOMException e) {
			LOG.error(e);
		} catch (IOException e) {
			LOG.error(e);
		}
		return;
	}
	
	private boolean createViewerXML() {
		File viewer = new File(downloadDirectory + "/data/viewer.xml");
		File assets = new File(downloadDirectory + "/assets/");
		viewer.getParentFile().mkdirs();
		Document doc = new Document();
		Element presentation = new Element("presentation");
		doc.setRootElement(presentation);
		Element slides = new Element("slides");
		presentation.addContent(slides);
		for(int i = 1; i <= assets.list().length; i++) {
			Element slide = new Element("slide");
			slide.addContent(new Element("content").setAttribute("url", "Slide" + i + ".swf"));
			slides.addContent(slide);
		}
		try {
			XMLOutputter out = new XMLOutputter();
			out.setFormat(Format.getPrettyFormat());
			out.output(doc, new FileOutputStream(viewer));
			return true;
		} catch (IOException e) {
			LOG.error("IOException", e);
		} 
		return false;
	}

	private boolean convertPDF2SWF() {
		Runtime runtime = Runtime.getRuntime();
		LOG.debug("generating SWFs from PDF");
		try {
			String directory = this.downloadDirectory +  File.separatorChar + "assets" + File.separatorChar;
			if(!new File(directory).exists())
			{
				new File(directory).mkdirs();
			}
			Process proc = runtime.exec("pdf2swf -o " + directory + "Slide%.swf " + this.slide.getAbsolutePath());
            InputStream stdin = proc.getInputStream();
            InputStreamReader isr = new InputStreamReader(stdin);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            LOG.debug("<OUTPUT>");
            while ( (line = br.readLine()) != null)
            	LOG.debug(line);
            LOG.debug("</OUTPUT>");
            int exitVal = proc.waitFor();            
            System.out.println("Generating SWFs from PDF ended in: " + exitVal);
        
			return true;
		} catch (IOException e) {
			LOG.error("IOException", e);
		} catch (InterruptedException e) {
			LOG.error("InterruptedException", e);
		}
		return false;
	}

	/**
	 * get the powerpoint file by a given URL
	 * 
	 * @param url the URL pointing to the ppt
	 * @param media_id the media_id of the slide
	 * @return a <code>File</code> pointing to the ppt on the machine 
	 */
	private File downloadPPT(String url, String media_id) {
		LOG.debug("downloading files");
		
		HttpClient client = new HttpClient();
		HttpMethod method = new GetMethod(url);
		File ppt = new File(HelperClass.getProperty("generalUploadFolder") + File.separatorChar + 
								HelperClass.getProperty("slideSubFolder") + File.separatorChar +
									media_id + ".ppt");
		try {
			int statusCode = client.executeMethod(method);
			if(statusCode == HttpStatus.SC_OK){
				InputStream in = method.getResponseBodyAsStream();
				FileOutputStream out = new FileOutputStream(ppt);
				byte[] buffer = new byte[1024];
				while(in.read(buffer)!= -1){
					out.write(buffer);
				}
				out.flush();
				out.close();
				in.close();
			}
		} catch (HttpException e) {
			LOG.error(e);
		} catch (IOException e) {
			LOG.error(e);
		} 
		LOG.debug("downloading files ready");
		return ppt;
	}
	
	/**
	 * combining back and foreground to one single file
	 * @return <code>true</code> on success, <code>false</code> else
	 * 
	 * @throws JDOMException
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	private boolean swfCombine() throws JDOMException, IOException {
		LOG.debug("combining SWFs");

		String path = downloadDirectory + "/assets/";
		Document viewer = new SAXBuilder().build(new File(downloadDirectory + "/data/viewer.xml"));
		Element root = viewer.getRootElement();
		String command = HelperClass.getProperty("swfCombine");
		
		List<Element> backgrounds = root.getChild("backgrounds").getChildren("background");
		List<Element> slides = root.getChild("slides").getChildren("slide");
		for(Element slide : slides) {
			String content = slide.getChild("content").getAttributeValue("url");
			int backgroundIndex = Integer.valueOf(slide.getChild("slideBackground").getAttributeValue("index"));
			commands.add(command + path + content + " " + path + backgrounds.get(backgroundIndex).getAttributeValue("url") +
					" " + path + content);
		}
		
		Runtime runtime = Runtime.getRuntime();
		for(String execute : commands) {
			LOG.debug("executing: " + execute);
			runtime.exec(execute);
		}
		
		LOG.debug("combining SWFs ready");
		return true;
	}
	
	/**
	 * download all files
	 * 
	 * @return <code>true</code> on success, <code>false</code> else
	 */
	private boolean downloadFiles() {
		LOG.debug("downloading files for " + media_id);
		
		//create neccassary folders
		new File(downloadDirectory).mkdir();
		new File(downloadDirectory + "/assets").mkdir();
		new File(downloadDirectory + "/data").mkdir();
		try {
			//try to move ppt to converted folder
			FileUtils.moveFileToDirectory(slide, new File(downloadDirectory), false);
		} catch (IOException e) {
			LOG.error("could not move ppt to mediadirectory, " + e);
		}
		try {
			ftp = new FTPClient();
			ftp.connect(HelperClass.getProperty("slideConverterURL"));
			ftp.login(username, password);
			if(ftp.getReplyCode() == 230)
	        {
				ftp.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
				ftp.setFileType(FTP.BINARY_FILE_TYPE);
	            ftp.changeWorkingDirectory(directory + "/result/data");
	            FTPFile files[] = ftp.listFiles();
	            for(int i = 0; i < files.length; i++)
	            {
	                FTPFile file = files[i];
	                if(file.getName().endsWith(".xml")) {
	                    ftp.retrieveFile(file.getName(), new FileOutputStream(downloadDirectory + "/data/" + file.getName()));
	                } else if(file.getName().endsWith(".swf")) {
	                	ftp.retrieveFile(file.getName(), new FileOutputStream(downloadDirectory + "/assets/" + file.getName()));
	                }
	            }
//	            ftp.changeWorkingDirectory("/" + directory + "/result/data/thumbs");
//	            LOG.debug(ftp.getReplyCode());
//	            files = ftp.listFiles();
//	            for(int i = 0; i < files.length; i++)
//	            {
//	            	FTPFile file = files[i];
//	                if(file.getName().endsWith(".jpg")) {
//	                    ftp.retrieveFile(file.getName(), new FileOutputStream(downloadDirectory + "/assets/" + file.getName()));
//	                }
//	            }

	        } else
	        {
	        	LOG.error("could not login: " + ftp.getReplyCode() + " - " + ftp.getReplyString());
	            return false;
	        }
			ftp.logout();
			ftp.disconnect();
		} catch (SocketException e) {
			LOG.error(e);
			return false;
		} catch (IOException e) {
			LOG.error(e);
			return false;
		}
		return true;
	}

	/**
	 * checks for conversion, waiting for a special file (/result/data/search.xml)
	 * @return <code>true</code> if conversion is done, <code>false</code> else
	 */
	private boolean checkForConversion() {
		String[] readyFiles;
		try {
			ftp = new FTPClient();
			ftp.connect(HelperClass.getProperty("slideConverterURL"));
			ftp.login(username, password);
			if(ftp.getReplyCode() == 230) {	//user logged in
				readyFiles = ftp.listNames(directory + "/result/data/search.xml");
				if(readyFiles != null && readyFiles.length == 1 && 
						readyFiles[0].equals(directory + "/result/data/search.xml")) {
					return true;
				}
			} else 
			{
				LOG.error("could not login: " + ftp.getReplyCode() + " - " + ftp.getReplyString());
				return false;
			}
		} catch (SocketException e) {
			LOG.error(e);
		} catch (IOException e) {
			LOG.error(e);
		}
		return false;
	}
	
	/**
	 * generates the generator.xml 
	 * 
	 * @return the generator file
	 */
	private File generateGeneratorFile() {
		String content = "<message>" +
						 	"<task>notifyPPT_Listener_Result</task> " +
						 	"<SeminarID>flurfunk-seminar</SeminarID>" + //beliebige ID 
						 "</message>";
		File generator = new File(slide.getParent() + sep + "generator.xml");
		if(generator.exists())
		{
			return generator;
		}
		try {
			FileWriter write = new FileWriter(generator);
			write.write(content);
			write.close();
		} catch (IOException e) {
			LOG.error(e);
		}
		return generator;
	}
	
	/**
	 * upload the slide and the generator.xml to server
	 * 
	 * @return <code>true</code> on success, <code>false</code> else
	 */
	private boolean upload() {
		try {
			LOG.debug("start uploading files for " + media_id);
			
			ftp = new FTPClient();
			ftp.connect(HelperClass.getProperty("slideConverterURL"));
			ftp.login("virtpresenter", "soutriv");
			if(ftp.getReplyCode() == 230) {	//user logged in
				ftp.makeDirectory(directory);
				if(ftp.getReplyCode() == 257) { //directory created
					ftp.setFileType(FTP.BINARY_FILE_TYPE);
					ftp.changeWorkingDirectory(directory);
					//upload slide and generator.xml
					FileInputStream slideIn = new FileInputStream(slide);
					ftp.storeFile(slide.getName(), slideIn);
					ftp.storeFile("generator.xml", new FileInputStream(generateGeneratorFile()));
					if(ftp.listFiles().length != 2) { //two files have to be on the server
						LOG.error("there are not 2 files on the server: " + ftp.getReplyCode() + " - " + 
								ftp.getReplyString());
						return false;
					}
					slideIn.close();
				} else {
					LOG.error("could not create directory: " + ftp.getReplyCode() + " - " + ftp.getReplyString());
					return false;
				}
			} else 
			{
				LOG.error("could not login: " + ftp.getReplyCode() + " - " + ftp.getReplyString());
				return false;
			}
			LOG.debug(ftp.getReplyCode()+"-"+ftp.getReplyString());
			ftp.logout();
			ftp.disconnect();
		} catch (SocketException e) {
			LOG.error(e);
		} catch (IOException e) {
			LOG.error(e);
		}
		return true;
	}
	
	/**
	 * start the conversion by calling the server on via telnet
	 * 
	 * @return <code>true</code> on success, <code>false</code> else
	 */ 
	private boolean startConversion() {
		LOG.debug("tell server to start conversion for " + media_id);
		try {
			TelnetClient tel = new TelnetClient();
			tel.connect(HelperClass.getProperty("slideConverterURL"), 1989);
			PrintStream out = new PrintStream(tel.getOutputStream());
	        out.println("test " + directory);
	        out.flush();
			tel.disconnect();
			return true;
		} catch (SocketException e) {
			LOG.error(e);
		} catch (IOException e) {
			LOG.error(e);
		}
		return false;
	}
	
}
